﻿// Author: Wilfred Pinto
// Website: http://projectsilverlight.blogspot.com

using SimpleViewer_SL.code;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using System.Xml;

namespace SimpleViewer_SL
{
    /// <summary>
    /// Implements the Thumbnails UserControl
    /// </summary>
    /// <event cref="PropertyChangedEventHandler">Property Changed Event</event>
    public partial class Thumbnails : UserControl, INotifyPropertyChanged, ISimpleViewerSLControl
    {
        #region INotifyPropertyChanged Members

        /// <inheritdoc />
        public event PropertyChangedEventHandler PropertyChanged;
        
        #endregion

        #region ISimpleViewerSLControl Members

        /// <inheritdoc />
        public double ControlWidth { get { return (_thumbSize.Width + (_borderPlusPaddingWidth * 2)) * _colCount; } }

        /// <inheritdoc />
        public double ControlHeight { get { return _staticControlHeight + ((_thumbSize.Height + (_borderPlusPaddingWidth * 2)) * _rowCount); } }

        #endregion

        Page _parent = null;
        int _borderWidth = 2;
        int _selectedThumbnailIndex = -1;
        int _rowCount = 0;
        int _colCount = 0;
        int _totalColCount = 0;
        int _numPages = 0;
        int _currPage = 0;
        Size _thumbSize = new Size { Width = 65, Height = 65 };
        int _borderPlusPaddingWidth = 0;
        int _staticControlHeight = 0;

        /// <param name="parent">The <c>Page</c> which instantiates this object</param>
        public Thumbnails(Page parent)
        {
            // Initialize variables
            _parent = parent;
            _rowCount = _parent.Settings.ThumbnailRows;
            _colCount = _parent.Settings.ThumbnailColumns;
            _totalColCount = (int)(Math.Ceiling((double)_parent.Settings.ImagesSettings.Count / _rowCount));
            _numPages = (int)(Math.Ceiling((double)_totalColCount / _colCount));
            _borderPlusPaddingWidth = _parent.Settings.ThumbPadding + _borderWidth;

            InitializeComponent();

            // The width of LayoutRoot is the actual display width
            // The width of LayoutThumbnailsViewport is the width of the actual 
            // thumbnails control even though only a part of it might be displayed
            LayoutRoot.Width = ControlWidth;
            LayoutThumbnailsViewport.Width = ControlWidth * _numPages;

            // Create the Layout and draw the thumbnails
            _staticControlHeight = CreateLayout();
            DrawThumbnails();

            // Enable the Navigation Button if required
            EnableDisableNavigationArrows();

            // Preload the full size images/photos
            PreloadImages();
        }

        /// <summary>
        /// Gets or sets the selected thumbnail index
        /// </summary>
        public int SelectedThumbnailIndex
        {
            get { return _selectedThumbnailIndex; }
            set
            {
                // Check if value is within bounds and not already selected
                if (value >= 0 && value < _parent.Settings.ImagesSettings.Count && value != _selectedThumbnailIndex)
                {
                    _selectedThumbnailIndex = value;

                    // Calculate the page number of the thumbnail
                    int page = GetThumbnailPage(_selectedThumbnailIndex);

                    // Scroll the thumbnails grid if required, to show the newly selected thumbnail
                    while (page < _currPage) ScrollLeft();
                    while (page > _currPage) ScrollRight();

                    // Remove the old selection
                    LayoutThumbnails.Children.Remove(SelectedBorder);

                    // Add the new selection
                    int thumbnailIndex = GetThumbnailIndexInPage(_selectedThumbnailIndex);
                    SelectedBorder.SetValue(Grid.RowProperty, thumbnailIndex / _colCount);
                    SelectedBorder.SetValue(Grid.ColumnProperty, (thumbnailIndex % _colCount) + (page * _colCount));
                    LayoutThumbnails.Children.Add(SelectedBorder);
                    SelectedBorder.Opacity = 1;

                    // Display the thumbnail caption
                    Caption.Text = _parent.Settings.ImagesSettings[_selectedThumbnailIndex].Caption;

                    // Call NotifyPropertyChanged when the source property is updated
                    NotifyPropertyChanged("SelectedThumbnailIndex");
                }
            }
        }

        /// <summary>
        /// NotifyPropertyChanged will fire the PropertyChanged event passing the
        /// source property that is being updated.
        /// </summary>
        /// <param name="propertyName">The source property to pass to the event</param>
        /// <example><code>NotifyPropertyChanged("SelectedThumbnailIndex");</code></example>
        /// <remarks>Only <c>"SelectedThumbnailIndex"</c> is passed currently</remarks>
        public void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Returns the page where the thumbnail is located (zero based)
        /// </summary>
        /// <remarks>
        /// Assume that there are 10 thumbnails and only 9 can be displayed in 1 page (3x3)
        /// The thumbnails 0 through 8 would return page 0
        /// The thumbnail 9 would return page 1
        /// </remarks>
        /// <param name="thumbnailIndex">The actual index of the thumbnail</param>
        /// <returns>The zero based page where the thumbnail is located</returns>
        protected int GetThumbnailPage(int thumbnailIndex) 
        { 
            return thumbnailIndex / (_rowCount * _colCount); 
        }
        
        /// <summary>
        /// Returns the index of the thumbnail in the page where it is located (zero based)
        /// </summary>
        /// <remarks>
        /// Assume that there are 10 thumbnails and only 9 can be displayed in 1 page (3x3)
        /// The thumbnails 0 through 8 would return index 0 to 8
        /// The thumbnail 9 would return index 0
        /// </remarks>
        /// <param name="thumbnailIndex">The actual index of the thumbnail</param>
        /// <returns>The zero based index of the thumbnail in the page where it is located</returns>
        protected int GetThumbnailIndexInPage(int thumbnailIndex) 
        { 
            return thumbnailIndex - (GetThumbnailPage(thumbnailIndex) * _rowCount * _colCount); 
        }

        /// <summary>
        /// Names the thumbnail in the object tree so that it can be found easily
        /// </summary>
        /// <param name="thumbnail">The thumbnail object</param>
        /// <param name="thumbnailIndex">The index of the thumbnail</param>
        protected void SetThumbnailNameAndTagProperty(Border thumbnail, int thumbnailIndex)
        {
            thumbnail.SetValue(NameProperty, "Thumbnail" + thumbnailIndex.ToString());
            thumbnail.Tag = thumbnailIndex.ToString();  // Tag can only handle Strings as of SL2 B1 release
        }

        /// <summary>
        /// Retrieves the thumbnail from the object tree
        /// </summary>
        /// <param name="thumbnailIndex">The index of the thumbnail</param>
        /// <returns>The thumbnail object</returns>
        protected Border GetThumbnail(int thumbnailIndex)
        {
            return (Border)LayoutThumbnails.FindName("Thumbnail" + thumbnailIndex.ToString());
        }

        /// <summary>
        /// Returns the index of the thumbnail given the thumbnail object
        /// </summary>
        /// <param name="thumbnail">The thumbnail object</param>
        /// <returns>Index of the thumbnail</returns>
        protected int GetThumbnailIndex(Border thumbnail)
        {
            return Int32.Parse((string)thumbnail.Tag);
        }

        /// <summary>
        /// Creates the Layout by allocating rows to each of the following
        /// gallery attributes: 
        /// <c>Title</c>, <c>Thumbnails</c>, <c>Navigation Buttons</c>, and <c>Caption</c>
        /// </summary>
        /// <returns>
        /// The height of the static components of the control - 
        /// <c>Title</c>, <c>Navigation Buttons</c>, and <c>Caption</c>
        /// </returns>
        protected int CreateLayout()
        {
            int rowNum = 0;
            int staticControlHeight = 0;

            if (!String.IsNullOrEmpty(_parent.Settings.Title))
            {
                // Allocate a row of 50 pixels height for the Title
                LayoutRoot.RowDefinitions.Add(new RowDefinition { Height = new GridLength(50) });
                Title.SetValue(Grid.RowProperty, rowNum++);
                staticControlHeight += 50;
            }

            // Allocate a row for Thumbnails - Height is not specified at this time
            LayoutRoot.RowDefinitions.Add(new RowDefinition());
            LayoutThumbnailsViewport.SetValue(Grid.RowProperty, rowNum++);

            if (_numPages > 0)
            {
                // Allocate a row of 30 pixels height for the Navigation Buttons
                LayoutRoot.RowDefinitions.Add(new RowDefinition { Height = new GridLength(30) });
                RightNav.SetValue(Grid.RowProperty, rowNum);
                LeftNav.SetValue(Grid.RowProperty, rowNum++);
                staticControlHeight += 30;
            }

            int i = 0;
            while (i < _parent.Settings.ImagesSettings.Count)
            {
                if (!String.IsNullOrEmpty(_parent.Settings.ImagesSettings[i++].Caption)) 
                {
                    // Allocate a row of 30 pixels height for the Caption
                    LayoutRoot.RowDefinitions.Add(new RowDefinition { Height = new GridLength(30) });
                    Caption.SetValue(Grid.RowProperty, rowNum++);
                    staticControlHeight += 30;
                    break;
                }
            }

            // Return the height of the static components of the control
            return staticControlHeight;
        }

        /// <summary>
        /// Displays the thumbnails
        /// </summary>
        protected void DrawThumbnails()
        {
            try
            {
                RightNavArrow.Fill = new SolidColorBrush(_parent.Settings.FrameColor);
                LeftNavArrow.Fill = new SolidColorBrush(_parent.Settings.FrameColor);
                SelectedBorder.BorderBrush = new SolidColorBrush(_parent.Settings.FrameColor);
                SelectingBorder.BorderBrush = new SolidColorBrush(_parent.Settings.FrameColor);
                Caption.Foreground = new SolidColorBrush(_parent.Settings.TextColor);
                Title.Foreground = new SolidColorBrush(_parent.Settings.TextColor);

                // Show the Title
                Title.Text = _parent.Settings.Title;

                // Create the complete (not paginated) grid layout for the thumbnails
                for (int row = 0; row < _rowCount; row++)
                {
                    LayoutThumbnails.RowDefinitions.Add(new RowDefinition { Height = new GridLength(_thumbSize.Height + (_borderPlusPaddingWidth * 2)) });
                }
                for (int col = 0; col < _colCount * _numPages; col++)
                {
                    LayoutThumbnails.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(_thumbSize.Width + (_borderPlusPaddingWidth * 2)) });
                }

                // The following logic ensures that the thumbnails are arranged
                // in the proper order taking pagination into account
                int thumbnailIndex = 0;
                int startCol = 0;
                for (int page = 0; page < _numPages; page++)
                {
                    for (int row = 0; row < _rowCount; row++)
                    {
                        for (int col = 0; col < _colCount && thumbnailIndex < _parent.Settings.ImagesSettings.Count; col++)
                        {
                            // Create each thumbnail
                            CreateThumbnail(row, col + startCol, thumbnailIndex);
                            thumbnailIndex++;
                        }
                    }
                    startCol += _colCount;
                }

                LeftNav.MouseLeftButtonDown += (sender, e) =>
                {
                    // To simulate button push
                    LeftNavArrowTranslateStoryboard.Begin();
                };

                LeftNav.MouseLeave += (sender, e) =>
                {
                    // Release the button push
                    LeftNavArrowTranslateStoryboard.Stop();
                };

                LeftNav.MouseLeftButtonUp += (sender, e) =>
                {
                    // Scroll Left
                    LeftNavArrowTranslateStoryboard.Stop();
                    ScrollLeft();
                };

                RightNav.MouseLeftButtonDown += (sender, e) =>
                {
                    // To simulate button push
                    RightNavArrowTranslateStoryboard.Begin();
                };

                RightNav.MouseLeave += (sender, e) =>
                {
                    // Release the button push
                    RightNavArrowTranslateStoryboard.Stop();
                };

                RightNav.MouseLeftButtonUp += (sender, e) =>
                {
                    // Scroll Right
                    RightNavArrowTranslateStoryboard.Stop();
                    ScrollRight();
                };
            }
            catch (Exception e)
            {
                HtmlPage.Window.Alert(e.Message);
            }
        }

        /// <summary>
        /// Create and displays a single thumbnail
        /// </summary>
        /// <param name="row">Row where the thumbnail should be displayed</param>
        /// <param name="col">Column where the thumbnail should be displayed</param>
        /// <param name="imageIndex">The index of the thumbnail</param>
        protected void CreateThumbnail(int row, int col, int imageIndex)
        {
            // Create a new thumbnail and set the source
            Image image = new Image();
            image.Source = new BitmapImage(new Uri(_parent.Settings.ThumbPath + _parent.Settings.ImagesSettings[imageIndex].Name, UriKind.Relative));

            // Create a border for the thumbnails
            Border thumbnail = new Border
            {
                Width = _thumbSize.Width + (_borderWidth * 2),
                Height = _thumbSize.Height + (_borderWidth * 2),
                BorderThickness = new Thickness(_borderWidth),
                BorderBrush = new SolidColorBrush(_parent.Settings.FrameColor),
                Margin = new Thickness(_parent.Settings.ThumbPadding),
                Opacity = 0.6
            };
            thumbnail.SetValue(Grid.RowProperty, row);
            thumbnail.SetValue(Grid.ColumnProperty, col);
            SetThumbnailNameAndTagProperty(thumbnail, imageIndex);

            thumbnail.Child = image;

            // Add the border to the Layout (the border contains the image)
            LayoutThumbnails.Children.Add(thumbnail);

            thumbnail.MouseLeftButtonDown += (sender, e) =>
            {
                // Simulate a button push effect if thumbnail is clicked 
                if (CanClickThumbnail((Border)sender))
                    StartThumbnailTranslateTransform((Border)sender);
            };

            thumbnail.MouseLeftButtonUp += (sender, e) =>
            {
                Rect LayoutRootRect = new Rect(0, 0, LayoutRoot.RenderSize.Width, LayoutRoot.RenderSize.Height);
                // Ignore the click that is captured outside the thumbnail viewport bounds
                if (LayoutRootRect.Contains(e.GetPosition(LayoutRoot)))
                {
                    // Release the button push effect
                    StopThumbnailTranslateTransform();

                    // Select the thumbnail since it was clicked
                    Border currentBorder = (Border)sender;
                    SelectedThumbnailIndex = GetThumbnailIndex(currentBorder);
                }
            };

            thumbnail.MouseLeave += (sender, e) =>
            {
                // Release the button push effect
                StopThumbnailTranslateTransform();
            };
        }

        /// <summary>
        /// Handles the keyboard keys
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">KeyEventArgs</param>
        /// <remarks>
        /// <c>Left Arrow Key</c>: Select the previous thumbnail<br />
        /// <c>Right Arrow Key</c>: Select the next thumbnail<br />
        /// <c>Up Arrow Key</c>: Select the thumbnail one page to the left<br />
        /// <c>Down Arrow Key</c>: Select the thumbnail one page to the right<br />
        /// <c>Home Key</c>: Select the first thumbnail<br />
        /// <c>End Key</c>: Select the last thumbnail
        /// </remarks>
        public void KeyboardHandler(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Left:
                    SelectedThumbnailIndex -= 1;
                    break;
                case Key.Right:
                    SelectedThumbnailIndex += 1;
                    break;
                case Key.Up:
                    SelectedThumbnailIndex -= _colCount;
                    break;
                case Key.Down:
                    SelectedThumbnailIndex += _colCount;
                    break;
                case Key.Home:
                    SelectedThumbnailIndex = 0;
                    break;
                case Key.End:
                    SelectedThumbnailIndex = _parent.Settings.ImagesSettings.Count - 1;
                    break;
            }
        }

        /// <summary>
        /// Gives the thumbnail a push button kind of effect on mouse click
        /// </summary>
        /// <param name="thumbnail">Thumbnail image</param>
        protected void StartThumbnailTranslateTransform(Border thumbnail)
        {
            ThumbnailTranslateStoryboard.Stop();

            // Add a translate transform to the thumbnail so that it can be animated
            TranslateTransform translation = new TranslateTransform();
            thumbnail.RenderTransform = translation;

            // Set the ThumbnailTranslateStoryboard's properties to
            // animate the thumbnail's translate transform

            Storyboard.SetTarget(X, translation);
            Storyboard.SetTargetProperty(X, new PropertyPath("X", new object[0]));

            Storyboard.SetTarget(Y, translation);
            Storyboard.SetTargetProperty(Y, new PropertyPath("Y", new object[0]));

            // Start the animation so that it looks like the thumbnail is being pressed
            ThumbnailTranslateStoryboard.Begin();
        }

        /// <summary>
        /// Stops the thumbnail push button effect
        /// </summary>
        protected void StopThumbnailTranslateTransform()
        {
            ThumbnailTranslateStoryboard.Stop();
        }

        /// <summary>
        /// Returns <c>true</c> if thumbnail can be clicked 
        /// </summary>
        /// <remarks>Currently selected thumbnail cannot be clicked</remarks>
        /// <param name="thumbnail">The thumbnail to compare against the currently selected thumbnail</param>
        /// <returns>
        /// <c>false</c> - if passed in thumbnail is same as selected thumbnail
        /// <c>true</c> - otherwise
        /// </returns>
        protected bool CanClickThumbnail(Border thumbnail)
        {
            // Check to see if a thumbnail is currently selected
            if (SelectedBorder.Opacity == 1)
            {
                // Compare the row values of the selected thumbnail and passed in thumbnail
                if (SelectedBorder.GetValue(Grid.RowProperty).ToString() == thumbnail.GetValue(Grid.RowProperty).ToString())
                {
                    // Compare the column values of the selected thumbnail and passed in thumbnail
                    if (SelectedBorder.GetValue(Grid.ColumnProperty).ToString() == thumbnail.GetValue(Grid.ColumnProperty).ToString())
                    {
                        // return false if selected thumbnail is same as passed in thumbnail
                        return false;
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Scrolls the thumbnails one page to the right, if possible
        /// </summary>
        protected void ScrollRight()
        {
            if (CanScrollRight)
            {
                _currPage++;

                // Animate the scroll
                GridScrollAnimation.To = -(_thumbSize.Width + (_borderPlusPaddingWidth * 2)) * _colCount * _currPage;
                GridScrollStoryboard.Begin();

                // Enable the Navigation buttons if possible
                EnableDisableNavigationArrows();
            }
        }

        /// <summary>
        /// Scrolls the thumbnails one page to the left, if possible
        /// </summary>
        protected void ScrollLeft()
        {
            if (CanScrollLeft)
            {
                _currPage--;

                // Animate the scroll
                GridScrollAnimation.To = -(_thumbSize.Width + (_borderPlusPaddingWidth * 2)) * _colCount * _currPage;
                GridScrollStoryboard.Begin();

                // Enable the Navigation buttons if possible
                EnableDisableNavigationArrows();
            }
        }

        /// <summary>
        /// Returns <c>true</c> if scrolling to the left is possible
        /// </summary>
        protected bool CanScrollRight { get { return ((_currPage + 1) < _numPages); } }
        
        /// <summary>
        /// Returns <c>true</c> if scrolling to the right is possible
        /// </summary>
        protected bool CanScrollLeft { get { return (_currPage > 0); } }

        /// <summary>
        /// Enables the Left Navigation button if scrolling to the left is possible<br />
        /// Enables the Right Navigation button if scrolling to the right is possible
        /// </summary>
        protected void EnableDisableNavigationArrows()
        {
            LeftNavArrow.Opacity = CanScrollLeft ? 1 : 0;
            RightNavArrow.Opacity = CanScrollRight ? 1 : 0;
        }

        /// <summary>
        /// Preloads the full size images to the browser cache in the background.
        /// Visual cue of download progress is provided by showing a progress bar on the thumbnail.
        /// Visual cue of download completion is provided by making the thumbnail fully opaque.
        /// </summary>
        /// <remarks>
        /// Silverlight downloads images to the browser cache and renders it. 
        /// Subsequent access to the images will come from the browser cache and hence
        /// the image won't be downloaded again.
        /// This feature of Silverlight is leveraged by this function. Instead of waiting
        /// for Silverlight to download the images, this routine will proactively download
        /// the photos to the browser cache. When Silverlight needs to render these images,
        /// it doesn't have to download it and hence rendering is faster.
        /// </remarks>
        protected void PreloadImages()
        {
            ProgressRectangle.Fill = new SolidColorBrush(_parent.Settings.FrameColor);
            ProgressRectangle.Margin = new Thickness(_borderPlusPaddingWidth, 0, _borderPlusPaddingWidth, _borderPlusPaddingWidth);

            // Create a timer that will preload one photo at a time
            DispatcherTimer _myDispatcherTimer = new DispatcherTimer();
            int preloadImageIndex = 0;
            WebClient preloadImageWebClient = new WebClient();

            // Handle each tick of the timer
            _myDispatcherTimer.Tick += (sender, e) =>
            {
                if (preloadImageIndex >= _parent.Settings.ImagesSettings.Count)
                {
                    // The timer can be stopped if all photos have been download
                    _myDispatcherTimer.Stop();
                    _myDispatcherTimer = null;
                }
                // This check ensures that only one photo is downloaded at a time
                else if (!preloadImageWebClient.IsBusy)
                {
                    int page = GetThumbnailPage(preloadImageIndex);
                    int thumbnailIndex = GetThumbnailIndexInPage(preloadImageIndex);

                    // The progress bar is reused since only one photo is downloaded at a time
                    LayoutThumbnails.Children.Remove(ProgressRectangle);
                    ProgressRectangle.SetValue(Grid.RowProperty, thumbnailIndex / _colCount);
                    ProgressRectangle.SetValue(Grid.ColumnProperty, (thumbnailIndex % _colCount) + (page * _colCount));
                    ProgressRectangle.Width = 0;
                    LayoutThumbnails.Children.Add(ProgressRectangle);
                    ProgressRectangle.Opacity = 1;

                    // Download the photo
                    preloadImageWebClient.OpenReadAsync(new Uri(_parent.Settings.ImagePath + _parent.Settings.ImagesSettings[preloadImageIndex].Name, UriKind.Relative));
                }
            };

            preloadImageWebClient.DownloadProgressChanged += (sender2, e2) =>
            {
                // Update the progress bar as the photo is getting downloaded
                ProgressRectangle.Width = (double)e2.ProgressPercentage;
            };

            // Download is complete!
            preloadImageWebClient.OpenReadCompleted += (sender3, e3) =>
            {
                Border thumbnail = GetThumbnail(preloadImageIndex);
                ProgressRectangle.Opacity = 0;
                if (thumbnail != null)
                {
                    // Make sure that the previously preloaded image is completely opaque
                    ThumbnailOpacityStoryboard.Stop();
                    if (preloadImageIndex > 0)
                    {
                        Border prevImage = GetThumbnail(preloadImageIndex - 1);
                        prevImage.Opacity = 1;
                    }

                    // Animate the currently preloaded thumbnail opacity so that
                    // it slowly becomes fully opaque
                    Storyboard.SetTarget(ThumbnailOpacityAnimation, thumbnail);
                    ThumbnailOpacityStoryboard.Begin();
                }

                // Preload the next image
                preloadImageIndex++;
            };

            // Start the timer for the next image to be preloaded
            _myDispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 50); // 50 msec
            _myDispatcherTimer.Start();
        }
    }
}
